﻿using ImageK;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO.Packaging;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ImageK.Process;

namespace ImageK.IO
{
    /** Writes a raw image described by a FileInfo object to an OutputStream. */
    public class ImageWriter
    {
        private FileInfo fi;
        private bool showProgressBar = true;
        private bool savingStack;

        public ImageWriter(FileInfo fi)
        {
            this.fi = fi;
        }

        private void showProgress(double progress)
        {
            if (showProgressBar)
                IJ.showProgress(progress);
        }

        void write8BitImage(BinaryWriter ot, byte[] pixels) 
        {

            int bytesWritten = 0;
            int size = fi.width*fi.height;
            int count = getCount(size);
		    while (bytesWritten<size) {
			    if ((bytesWritten + count)>size)
				    count = size - bytesWritten;
			    //System.out.println(bytesWritten + " " + count + " " + size);
			    ot.Write(pixels, bytesWritten, count);
                bytesWritten += count;
			    showProgress((double) bytesWritten/size);
            }
        }

        void write8BitStack(BinaryWriter ot, Object[] stack)
        {
            showProgressBar = false;
            savingStack = true;
		        for (int i=0; i<fi.nImages; i++) {
                IJ.ShowStatus("Writing: " + (i+1) + "/" + fi.nImages);
                write8BitImage(ot, (byte[])stack[i]);
                IJ.showProgress((double)(i+1)/fi.nImages);
            }
        }

        void write8BitVirtualStack(BinaryWriter ot, VirtualStack virtualStack) 
        {
            showProgressBar = false;
            bool flip = "FlipTheseImages".Equals(fi.fileName);
		        for (int i=1; i<=fi.nImages; i++) {
                IJ.ShowStatus("Writing: " + i + "/" + fi.nImages);
                ImageProcessor ip = virtualStack.getProcessor(i);
                if (flip) ip.flipVertical();
                byte[] pixels = (byte[])ip.getPixels();
                write8BitImage(ot, pixels);
                IJ.showProgress((double)i/fi.nImages);
            }
        }

        void write16BitImage(BinaryWriter ot, short[] pixels)
        {
		    long bytesWritten = 0L;
		    long size = 2L*fi.width*fi.height;
		    int count = getCount(size);
		    byte[]
            buffer = new byte[count];

            while (bytesWritten<size)
            {
                if ((bytesWritten + count)>size)
                    count = (int)(size-bytesWritten);
                int j = (int)(bytesWritten/2L);
                int value;
                if (fi.intelByteOrder)
                    for (int i = 0; i < count; i+=2)
                    {
                        value = pixels[j];
                        buffer[i] = (byte)value;
                        buffer[i+1] = (byte)(value>>8);
                        j++;
                    }
                else
                    for (int i = 0; i < count; i+=2)
                    {
                        value = pixels[j];
                        buffer[i] = (byte)(value>>8);
                        buffer[i+1] = (byte)value;
                        j++;
                    }
			            ot.Write(buffer, 0, count);
                bytesWritten += count;
                showProgress((double)bytesWritten/size);
            }
	    }
	
	    void write16BitStack(BinaryWriter ot, Object[] stack) 
        {
            showProgressBar = false;
		        for (int i=0; i<fi.nImages; i++) {
                IJ.ShowStatus("Writing: " + (i+1) + "/" + fi.nImages);
                write16BitImage(ot, (short[])stack[i]);
                IJ.showProgress((double)(i+1)/fi.nImages);
            }
        }

        void write16BitVirtualStack(BinaryWriter ot, VirtualStack virtualStack) 
        {
            showProgressBar = false;
            bool flip = "FlipTheseImages".Equals(fi.fileName);
		        for (int i=1; i<=fi.nImages; i++) {
                IJ.ShowStatus("Writing: " + i + "/" + fi.nImages);
                ImageProcessor ip = virtualStack.getProcessor(i);
                if (flip) ip.flipVertical();
                short[] pixels = (short[])ip.getPixels();
                write16BitImage(ot, pixels);
                IJ.showProgress((double)i/fi.nImages);
            }
        }

        void writeRGB48Image(BinaryWriter ot, Object[] stack) 
        {
		        short[]
            r = (short[])stack [0];
		        short[]
            g = (short[])stack [1];
		        short[]
            b = (short[])stack [2];
		        int size = fi.width*fi.height;
		        int count = fi.width*6;
		        byte[]
            buffer = new byte[count];
            for (int line = 0; line<fi.height; line++)
            {
                int index2 = 0;
                int index1 = line*fi.width;
                int value;
                if (fi.intelByteOrder)
                {
                    for (int i = 0; i<fi.width; i++)
                    {
                        value = r[index1];
                        buffer[index2++] = (byte)value;
                        buffer[index2++] = (byte)(value>>8);
                        value = g[index1];
                        buffer[index2++] = (byte)value;
                        buffer[index2++] = (byte)(value>>8);
                        value = b[index1];
                        buffer[index2++] = (byte)value;
                        buffer[index2++] = (byte)(value>>8);
                        index1++;
                    }
                }
                else
                {
                    for (int i = 0; i<fi.width; i++)
                    {
                        value = r[index1];
                        buffer[index2++] = (byte)(value>>8);
                        buffer[index2++] = (byte)value;
                        value = g[index1];
                        buffer[index2++] = (byte)(value>>8);
                        buffer[index2++] = (byte)value;
                        value = b[index1];
                        buffer[index2++] = (byte)(value>>8);
                        buffer[index2++] = (byte)value;
                        index1++;
                    }
                }
			    ot.Write(buffer, 0, count);
            }
	    }

        void writeFloatImage(BinaryWriter ot, float[] pixels) 
        {
		    long bytesWritten = 0L;
		    long size = 4L*fi.width*fi.height;
		    int count = getCount(size);
		    byte[]
            buffer = new byte[count];
            int tmp;

            while (bytesWritten<size)
            {
                if ((bytesWritten + count)>size)
                    count = (int)(size-bytesWritten);
                int j = (int)(bytesWritten/4L);
                if (fi.intelByteOrder)
                    for (int i = 0; i < count; i+=4)
                    {
                        tmp = BitConverter.SingleToInt32Bits(pixels[j]);
                        buffer[i]   = (byte)tmp;
                        buffer[i+1] = (byte)(tmp>>8);
                        buffer[i+2] = (byte)(tmp>>16);
                        buffer[i+3] = (byte)(tmp>>24);
                        j++;
                    }
                else
                    for (int i = 0; i < count; i+=4)
                    {
                        tmp = BitConverter.SingleToInt32Bits(pixels[j]);
                        buffer[i]   = (byte)(tmp>>24);
                        buffer[i+1] = (byte)(tmp>>16);
                        buffer[i+2] = (byte)(tmp>>8);
                        buffer[i+3] = (byte)tmp;
                        j++;
                    }
			    ot.Write(buffer, 0, count);
                bytesWritten += count;
                showProgress((double)bytesWritten/size);
            }
	    }
	
        private int getCount(long imageSize)
        {
            if (savingStack || imageSize<4L)
                return (int)imageSize;
            int count = (int)(imageSize/50L);
            if (count<65536)
                count = 65536;
            if (count>imageSize)
                count = (int)imageSize;
            count = (count/4)*4;
            if (IJ.debugMode) IJ.log("ImageWriter: "+imageSize+" "+count+" "+imageSize/50);
            return count;
        }

        void writeFloatStack(BinaryWriter ot, Object[] stack)
        {
            showProgressBar = false;
		        for (int i=0; i<fi.nImages; i++) {
                IJ.ShowStatus("Writing: " + (i+1) + "/" + fi.nImages);
                writeFloatImage(ot, (float[])stack[i]);
                IJ.showProgress((double)(i+1)/fi.nImages);
            }
        }

        void writeFloatVirtualStack(BinaryWriter ot, VirtualStack virtualStack) 
        {
            showProgressBar = false;
            bool flip = "FlipTheseImages".Equals(fi.fileName);
		        for (int i=1; i<=fi.nImages; i++) {
                IJ.ShowStatus("Writing: " + i + "/" + fi.nImages);
                ImageProcessor ip = virtualStack.getProcessor(i);
                if (flip) ip.flipVertical();
                float[] pixels = (float[])ip.getPixels();
                writeFloatImage(ot, pixels);
                IJ.showProgress((double)i/fi.nImages);
            }
        }

        void writeRGBImage(BinaryWriter ot, int[] pixels) 
        {
		    long bytesWritten = 0L;
		    long size = 3L*fi.width*fi.height;
		    int count = fi.width*24;
		    byte[]
            buffer = new byte[count];
            while (bytesWritten<size)
            {
                if ((bytesWritten+count)>size)
                    count = (int)(size-bytesWritten);
                int j = (int)(bytesWritten/3L);
                for (int i = 0; i<count; i+=3)
                {
                    buffer[i]   = (byte)(pixels[j]>>16);    //red
                    buffer[i+1] = (byte)(pixels[j]>>8); //green
                    buffer[i+2] = (byte)pixels[j];      //blue
                    j++;
                }
			    ot.Write(buffer, 0, count);
                bytesWritten += count;
                showProgress((double)bytesWritten/size);
            }
	    }
	
        void writeRGBStack(BinaryWriter ot, Object[] stack)
        {
            showProgressBar = false;
		        for (int i=0; i<fi.nImages; i++) {
                IJ.ShowStatus("Writing: " + (i+1) + "/" + fi.nImages);
                writeRGBImage(ot, (int[])stack[i]);
                IJ.showProgress((double)(i+1)/fi.nImages);
            }
        }

        void writeRGBVirtualStack(BinaryWriter ot, VirtualStack virtualStack) 
        {
            showProgressBar = false;
            bool flip = "FlipTheseImages".Equals(fi.fileName);
		        for (int i=1; i<=fi.nImages; i++) {
                IJ.ShowStatus("Writing: " + i + "/" + fi.nImages);
                ImageProcessor ip = virtualStack.getProcessor(i);
                if (flip) ip.flipVertical();
                int[] pixels = (int[])ip.getPixels();
                writeRGBImage(ot, pixels);
                IJ.showProgress((double)i/fi.nImages);
            }
        }

        /** Writes the image to the specified OutputStream.
            The OutputStream is not closed. The fi.pixels field
            must contain the image data. If fi.nImages>1
            then fi.pixels must be a 2D array, for example an
            array of images returned by ImageStack.getImageArray()).
            The fi.offset field is ignored. */
        public void write(BinaryWriter ot)
        {
		    if (fi.pixels==null && fi.virtualStack==null)
				    throw new IOException("ImageWriter: fi.pixels==null");
            if (fi.nImages>1 && fi.virtualStack==null && !(fi.pixels is object[]))
				    throw new IOException("ImageWriter: fi.pixels not a stack");
            if (fi.width*fi.height*fi.getBytesPerPixel()<26214400)
                showProgressBar = false; // don't show progress bar if image<25MB
            switch (fi.fileType)
            {
                case FileInfo.GRAY8:
                case FileInfo.COLOR8:
                    if (fi.nImages>1 && fi.virtualStack!=null)
                        write8BitVirtualStack(ot, fi.virtualStack);
                    else if (fi.nImages>1)
                        write8BitStack(ot, (Object[])fi.pixels);
                    else
                        write8BitImage(ot, (byte[])fi.pixels);
                    break;
                case FileInfo.GRAY16_SIGNED:
                case FileInfo.GRAY16_UNSIGNED:
                    if (fi.nImages>1 && fi.virtualStack!=null)
                        write16BitVirtualStack(ot, fi.virtualStack);
                    else if (fi.nImages>1)
                        write16BitStack(ot, (Object[])fi.pixels);
                    else
                        write16BitImage(ot, (short[])fi.pixels);
                    break;
                case FileInfo.RGB48:
                    writeRGB48Image(ot, (Object[])fi.pixels);
                    break;
                case FileInfo.GRAY32_FLOAT:
                    if (fi.nImages>1 && fi.virtualStack!=null)
                        writeFloatVirtualStack(ot, fi.virtualStack);
                    else if (fi.nImages>1)
                        writeFloatStack(ot, (Object[])fi.pixels);
                    else
                        writeFloatImage(ot, (float[])fi.pixels);
                    break;
                case FileInfo.RGB:
                    if (fi.nImages>1 && fi.virtualStack!=null)
                        writeRGBVirtualStack(ot, fi.virtualStack);
                    else if (fi.nImages>1)
                        writeRGBStack(ot, (Object[])fi.pixels);
                    else
                        writeRGBImage(ot, (int[])fi.pixels);
                    break;
                default:
                    break;
            }
            savingStack = false;
	    }
	        
    }
}
